home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994…tember: Reference Library / Dev.CD Sep 94.toast / Periodicals / develop / develop Issue 16 / develop 16 code / Number Formatting / Numbers.p < prev    next >
Encoding:
Text File  |  1993-09-15  |  15.1 KB  |  597 lines  |  [TEXT/MPS ]

  1. PROGRAM Numbers;
  2.  
  3.     USES
  4.         Script, Resources, Memory, Errors, GestaltEqu, Packages, SANE, UFailure;
  5.  
  6.     VAR
  7.         gDefaultDecimalSeparator: Char;
  8.         gDefaultThousandsSeparator: Char;
  9.  
  10.  
  11.     PROCEDURE InitializeDefaultNumberSeparators;
  12.  
  13.         VAR
  14.             theItl0Handle: Handle;
  15.  
  16.     BEGIN
  17.         theItl0Handle := GetResource('itl0', GetScript(smSystemScript, smScriptNumber));
  18.         FailNILResource(theItl0Handle);
  19.         WITH Intl0Hndl(theItl0Handle)^^ DO BEGIN
  20.                 IF (decimalPt IN ['0'..'9', Char(0), '-']) OR (thousSep IN ['0'..'9', '-']) OR (decimalPt = thousSep) THEN
  21.                     FailOSErr(paramErr);
  22.                 gDefaultDecimalSeparator := decimalPt;
  23.                 gDefaultThousandsSeparator := thousSep;
  24.             END;
  25.     END;
  26.  
  27.  
  28.     PROCEDURE LocalizeNumberString (VAR theString: Str255);
  29.  
  30.         VAR
  31.             boundary: Integer;
  32.             separatorString: STRING[1];
  33.             minusOffset: Integer;
  34.  
  35.     BEGIN
  36.         separatorString := ',';
  37.         separatorString[1] := gDefaultThousandsSeparator;
  38.  
  39.         boundary := Pos('.', theString);
  40.         IF boundary <> 0 THEN
  41.             theString[boundary] := gDefaultDecimalSeparator
  42.         ELSE
  43.             boundary := Length(theString) + 1;
  44.  
  45.         IF gDefaultThousandsSeparator <> Char(0) THEN BEGIN
  46.                 IF theString[1] = '-' THEN
  47.                     minusOffset := 1
  48.                 ELSE
  49.                     minusOffset := 0;
  50.                 WHILE boundary > 4 + minusOffset DO BEGIN
  51.                         theString := Concat(Copy(theString, 1, boundary - 4), separatorString, Copy(theString, boundary - 3, Length(theString) - boundary + 4));
  52.                         boundary := boundary - 3;
  53.                     END;
  54.             END;
  55.     END;
  56.  
  57.  
  58.     PROCEDURE IntegerToLocalString (theNumber: LongInt; VAR theString: Str255);
  59.  
  60.     BEGIN
  61.         NumToString(theNumber, theString);
  62.         LocalizeNumberString(theString);
  63.     END;
  64.  
  65.  
  66.     PROCEDURE ExtendedToLocalString (theNumber: Extended; decimalDigits: Integer; VAR theString: Str255);
  67.  
  68.         VAR
  69.             theDecForm: DecForm;
  70.  
  71.     BEGIN
  72.         WITH theDecForm DO BEGIN
  73.                 style := fixedDecimal;
  74.                 digits := decimalDigits;
  75.             END;
  76.         Num2Str(theDecForm, theNumber, DecStr(theString));
  77.         LocalizeNumberString(theString);
  78.     END;
  79.  
  80.  
  81.     VAR
  82.         gUserNumberPartsTable: NumberPartsPtr;
  83.         gReferenceNumberPartsTable: NumberPartsPtr;
  84.  
  85.  
  86.     VAR
  87.         gSystemVersion: LongInt;
  88.  
  89.  
  90.     FUNCTION GetUserItl4: Handle;
  91.  
  92.         VAR
  93.             theItl4Handle: Handle;
  94.             systemScript: ScriptCode;
  95.             tableOffset, tableLength: LongInt;
  96.             theItl0Handle: Handle;
  97.             theResID: Integer;
  98.             theResType: ResType;
  99.             theResName: Str255;
  100.  
  101.     BEGIN
  102.  
  103.         IF gSystemVersion >= $0710 THEN BEGIN
  104.                 systemScript := GetEnvirons(smSysScript);
  105.                 IUGetItlTable(systemScript, iuNumberPartsTable, theItl4Handle, tableOffset, tableLength);
  106.                 FailNILResource(theItl4Handle);
  107.             END
  108.  
  109.         ELSE BEGIN
  110.                 theItl0Handle := IUGetIntl(0);
  111.                 FailNILResource(theItl0Handle);
  112.                 GetResInfo(theItl0Handle, theResID, theResType, theResName);
  113.                 theItl4Handle := GetResource('itl4', theResID);
  114.  
  115.                 IF ResError = resNotFound THEN BEGIN
  116.                         CASE theResID OF
  117.                             6: 
  118.                                 theResID := 5;    { Netherlands }
  119.                             30777, 56, 57: 
  120.                                 theResID := 30776; { Czechoslovakia }
  121.                             OTHERWISE
  122.                                 theResID := verUS;
  123.                         END;
  124.                         theItl4Handle := GetResource('itl4', theResID);
  125.                         FailNILResource(theItl4Handle);
  126.                     END
  127.                 ELSE
  128.                     FailNILResource(theItl4Handle);
  129.             END;
  130.         GetUserItl4 := theItl4Handle;
  131.     END;
  132.  
  133.  
  134.     PROCEDURE CheckDefaultNumberSeparators (userNumberPartsTable: NumberPartsPtr);
  135.  
  136.         CONST
  137.             testString = '0';
  138.  
  139.         VAR
  140.             formatRecord: NumFormatString;
  141.             result: FormatStatus;
  142.  
  143.     BEGIN
  144.         result := Str2Format(testString, userNumberPartsTable^, formatRecord);
  145.         IF FormatResultType(result) <> fFormatOK THEN
  146.             FailOSErr(paramErr);
  147.     END;
  148.  
  149.  
  150.     FUNCTION ExtractNumberPartsTable (theItl4Handle: Handle): NumberPartsPtr;
  151.  
  152.         VAR
  153.             tableOffset, tableLength: LongInt;
  154.             theTable: Ptr;
  155.  
  156.     BEGIN
  157.         WITH NItl4Handle(theItl4Handle)^^ DO BEGIN
  158.                 tableOffset := defPartsOffset;
  159.                 tableLength := defPartsLength;
  160.             END;
  161.         theTable := NewPtr(tableLength);
  162.         FailNil(theTable);
  163.         LoadResource(theItl4Handle); { Might have been purged since we got hold of it }
  164.         FailResError;
  165.         BlockMove(Ptr(LongInt(theItl4Handle^) + tableOffset), theTable, tableLength);
  166.         ExtractNumberPartsTable := NumberPartsPtr(theTable);
  167.     END;
  168.  
  169.  
  170.     PROCEDURE InitializeNumberPartsTables;
  171.  
  172.         VAR
  173.             userItl4, usItl4: Handle;
  174.  
  175.     BEGIN
  176.         userItl4 := GetUserItl4;
  177.         usItl4 := GetResource('itl4', verUS);
  178.         FailNILResource(usItl4);
  179.         gUserNumberPartsTable := ExtractNumberPartsTable(userItl4);
  180.         CheckDefaultNumberSeparators(gUserNumberPartsTable);
  181.  
  182.         IF (usItl4 = userItl4) AND ((gSystemVersion < $0710) OR ((gDefaultDecimalSeparator = '.') AND (gDefaultThousandsSeparator = ','))) THEN
  183.             gReferenceNumberPartsTable := gUserNumberPartsTable
  184.         ELSE BEGIN
  185.                 gReferenceNumberPartsTable := ExtractNumberPartsTable(usItl4);
  186.                 gReferenceNumberPartsTable^.data[tokDecPoint].a[1] := '.';
  187.                 gReferenceNumberPartsTable^.data[tokThousands].a[1] := ',';
  188.             END;
  189.     END;
  190.  
  191.  
  192.     PROCEDURE DisposeNumberPartsTables;
  193.  
  194.     BEGIN
  195.         IF gReferenceNumberPartsTable <> gUserNumberPartsTable THEN
  196.             DisposPtr(Ptr(gReferenceNumberPartsTable));
  197.         DisposPtr(Ptr(gUserNumberPartsTable));
  198.         gReferenceNumberPartsTable := NIL;
  199.         gUserNumberPartsTable := NIL;
  200.     END;
  201.  
  202.  
  203.     PROCEDURE StringToFormatRecord (formatString: Str255; useAlternateNumerals: Boolean; theNumberPartsTable: NumberPartsPtr; VAR formatRecord: NumFormatString);
  204.  
  205.         VAR
  206.             result: FormatStatus;
  207.             oldChar: WideChar;
  208.             i: Integer;
  209.  
  210.     BEGIN
  211.         IF useAlternateNumerals THEN BEGIN
  212.                 oldChar := theNumberPartsTable^.data[tokNonLeader];
  213.                 theNumberPartsTable^.data[tokNonLeader].b := Ord('1');
  214.                 FOR i := 1 TO Length(formatString) DO
  215.                     IF formatString[i] = '#' THEN
  216.                         formatString[i] := '1';
  217.             END;
  218.         result := Str2Format(formatString, theNumberPartsTable^, formatRecord);
  219.         IF useAlternateNumerals THEN
  220.             theNumberPartsTable^.data[tokNonLeader] := oldChar;
  221.         IF FormatResultType(result) <> fFormatOK THEN
  222.             FailOSErr(paramErr);
  223.     END;
  224.  
  225.  
  226.     FUNCTION HasAlternateNumerals (aNumberPartsTable: NumberPartsPtr): Boolean;
  227.  
  228.     BEGIN
  229.         HasAlternateNumerals := aNumberPartsTable^.altNumTable.data[0].b <> Ord('0');
  230.     END;
  231.  
  232.  
  233.     PROCEDURE PredefinedStringToFormatRecord (predefinedFormatString: Str255; useAlternateNumerals: Boolean; VAR formatRecord: NumFormatString);
  234.  
  235.     BEGIN
  236.         StringToFormatRecord(predefinedFormatString, useAlternateNumerals, gReferenceNumberPartsTable, formatRecord);
  237.     END;
  238.  
  239.  
  240.     PROCEDURE FormatRecordToUserString (formatRecord: NumFormatString; VAR userFormatString: Str255);
  241.  
  242.         VAR
  243.             result: FormatStatus;
  244.             positions: TripleInt;
  245.  
  246.     BEGIN
  247.         result := Format2Str(formatRecord, gUserNumberPartsTable^, userFormatString, positions);
  248.         IF FormatResultType(result) <> fFormatOK THEN
  249.             FailOSErr(paramErr);
  250.     END;
  251.  
  252.  
  253.     PROCEDURE UserStringToFormatRecord (userFormatString: Str255; useAlternateNumerals: Boolean; VAR formatRecord: NumFormatString);
  254.  
  255.     BEGIN
  256.         StringToFormatRecord(userFormatString, useAlternateNumerals, gUserNumberPartsTable, formatRecord);
  257.     END;
  258.  
  259.  
  260.     PROCEDURE FormatNumber (theNumber: Extended; theFormatRecord: NumFormatString; VAR theString: Str255);
  261.  
  262.         VAR
  263.             result: FormatStatus;
  264.  
  265.     BEGIN
  266.         result := FormatX2Str(theNumber, theFormatRecord, gUserNumberPartsTable^, theString);
  267.         IF FormatResultType(result) <> fFormatOK THEN
  268.             FailOSErr(paramErr);
  269.     END;
  270.  
  271.  
  272.     PROCEDURE UnlocalizeNumberString (VAR theString: Str255; allowDecimal: Boolean);
  273.  
  274.         VAR
  275.             delta: Integer;
  276.             i: Integer;
  277.             theChar: Char;
  278.  
  279.     BEGIN
  280.         delta := 0;
  281.         FOR i := 1 TO Length(theString) DO BEGIN
  282.                 theChar := theString[i];
  283.                 IF (theChar >= '0') & (theChar <= '9') THEN
  284.                     theString[i - delta] := theChar
  285.                 ELSE IF (theChar = '-') & (i = 1) THEN
  286.                     theString[i - delta] := theChar
  287.                 ELSE IF theChar = gDefaultThousandsSeparator THEN
  288.                     delta := delta + 1
  289.                 ELSE IF theChar = gDefaultDecimalSeparator THEN BEGIN
  290.                         IF allowDecimal THEN BEGIN
  291.                                 allowDecimal := FALSE; { one is enough }
  292.                                 theString[i - delta] := '.';
  293.                             END
  294.                         ELSE
  295.                             FailOSErr(paramErr)
  296.                     END
  297.                 ELSE
  298.                     FailOSErr(paramErr);
  299.             END;
  300.         theString[0] := Char(Length(theString) - delta);
  301.         IF Length(theString) = 0 THEN
  302.             FailOSErr(paramErr);
  303.     END;
  304.  
  305.  
  306.     PROCEDURE LocalStringToInteger (theString: Str255; VAR theNumber: LongInt);
  307.  
  308.     BEGIN
  309.         UnlocalizeNumberString(theString, FALSE);
  310.         StringToNum(theString, theNumber);
  311.     END;
  312.  
  313.  
  314.     PROCEDURE LocalStringToExtended (theString: Str255; VAR theNumber: Extended);
  315.  
  316.     BEGIN
  317.         UnlocalizeNumberString(theString, TRUE);
  318.         theNumber := Str2Num(theString);
  319.     END;
  320.  
  321.  
  322.     FUNCTION InterpretExtended (theString: Str255; theFormatRecord: NumFormatString; VAR theNumber: Extended): Boolean;
  323.  
  324.         VAR
  325.             result: FormatStatus;
  326.  
  327.     BEGIN
  328.         result := FormatStr2X(theString, theFormatRecord, gUserNumberPartsTable^, theNumber);
  329.         InterpretExtended := FormatResultType(result) = fFormatOK;
  330.     END;
  331.  
  332.  
  333.     FUNCTION InterpretInteger (theString: Str255; theFormatRecord: NumFormatString; VAR theNumber: LongInt): Boolean;
  334.  
  335.         VAR
  336.             result: FormatStatus;
  337.             theExtended: Extended;
  338.  
  339.         CONST
  340.             minLongInt = -2147483648;
  341.             maxLongInt = 2147483647;
  342.  
  343.     BEGIN
  344.         result := FormatStr2X(theString, theFormatRecord, gUserNumberPartsTable^, theExtended);
  345.         IF (FormatResultType(result) = fFormatOK) & (theExtended >= minLongInt) & (theExtended <= maxLongInt) THEN BEGIN
  346.                 theNumber := Num2LongInt(theExtended);
  347.                 InterpretInteger := TRUE;
  348.             END
  349.         ELSE
  350.             InterpretInteger := FALSE;
  351.     END;
  352.  
  353.  
  354.     PROCEDURE CheckConfiguration;
  355.  
  356.         VAR
  357.             response: LongInt;
  358.  
  359.     BEGIN
  360.         FailOSErr(Gestalt(gestaltSystemVersion, response));
  361.         gSystemVersion := response; { uses low word only }
  362.         IF gSystemVersion < $0700 THEN BEGIN
  363.                 Writeln('### This sample requires at least System 7.0');
  364.                 ExitProgram;
  365.             END;
  366.     END;
  367.  
  368.  
  369.     PROCEDURE FailEOF;
  370.  
  371.     BEGIN
  372.         IF EOF THEN BEGIN
  373.                 Writeln('### encountered end of file');
  374.                 ExitProgram;
  375.             END;
  376.     END;
  377.  
  378.  
  379.     PROCEDURE DoDefaultFormattingTest;
  380.  
  381.         PROCEDURE WriteDefaultInteger (theNumber: LongInt);
  382.             VAR
  383.                 theString: Str255;
  384.         BEGIN
  385.             IntegerToLocalString(theNumber, theString);
  386.             Writeln(theString);
  387.         END;
  388.  
  389.         PROCEDURE WriteDefaultExtended (theNumber: Extended);
  390.             VAR
  391.                 theString: Str255;
  392.         BEGIN
  393.             ExtendedToLocalString(theNumber, 2, theString);
  394.             Writeln(theString);
  395.         END;
  396.  
  397.         PROCEDURE ReadDefaultInteger (VAR theInteger: LongInt);
  398.             VAR
  399.                 theString: Str255;
  400.         BEGIN
  401.             FailEOF;
  402.             ReadLn(theString);
  403.             LocalStringToInteger(theString, theInteger);
  404.         END;
  405.  
  406.         PROCEDURE ReadDefaultExtended (VAR theExtended: Extended);
  407.             VAR
  408.                 theString: Str255;
  409.         BEGIN
  410.             FailEOF;
  411.             ReadLn(theString);
  412.             LocalStringToExtended(theString, theExtended);
  413.         END;
  414.  
  415.         VAR
  416.             theLongInt: LongInt;
  417.             theExtended: Extended;
  418.  
  419.     BEGIN
  420.         WriteLn('Testing default formatting routines:');
  421.         WriteLn('Writing test numbers:');
  422.         WriteDefaultInteger(123);
  423.         WriteDefaultInteger(-123);
  424.         WriteDefaultInteger(1234);
  425.         WriteDefaultInteger(-1234);
  426.         WriteDefaultInteger(-123456789);
  427.         WriteDefaultInteger(0);
  428.         WriteDefaultExtended(123.456);
  429.         WriteDefaultExtended(7123.456);
  430.         WriteDefaultExtended(-123.456);
  431.         WriteDefaultExtended(-7123.456);
  432.         WriteDefaultExtended(-123456789.456);
  433.         WriteDefaultExtended(0);
  434.  
  435.         Writeln('Please enter integer numbers - 0 will skip to next test');
  436.         REPEAT
  437.             ReadDefaultInteger(theLongInt);
  438.             WriteDefaultInteger(theLongInt);
  439.         UNTIL theLongInt = 0;
  440.  
  441.         Writeln('Please enter floating point numbers - 0 will skip to next test');
  442.         REPEAT
  443.             ReadDefaultExtended(theExtended);
  444.             WriteDefaultExtended(theExtended);
  445.         UNTIL theExtended = 0.0;
  446.  
  447.     END;
  448.  
  449.  
  450.     CONST
  451.         kUSFloatFormatString = '###,###.##;-###,###.##;0.##';
  452.         kUSIntegerFormatString = '###,###;-###,###;0';
  453.         kUSScientificFormatString = '#.###e+##;-#.###e-##;0.';
  454.  
  455.  
  456.     VAR
  457.         theFloatFormatRecord: NumFormatString;
  458.         theIntegerFormatRecord: NumFormatString;
  459.         theScientificFormatRecord: NumFormatString;
  460.         gUseAlternateNumerals: Boolean;
  461.  
  462.  
  463.     PROCEDURE DoFormatStringTest;
  464.  
  465.         VAR
  466.             theString: Str255;
  467.  
  468.     BEGIN
  469.         WriteLn('Testing format specification conversions:');
  470.         PredefinedStringToFormatRecord(kUSFloatFormatString, gUseAlternateNumerals, theFloatFormatRecord);
  471.         PredefinedStringToFormatRecord(kUSIntegerFormatString, gUseAlternateNumerals, theIntegerFormatRecord);
  472.         PredefinedStringToFormatRecord(kUSScientificFormatString, gUseAlternateNumerals, theScientificFormatRecord);
  473.         FormatRecordToUserString(theFloatFormatRecord, theString);
  474.         Writeln('US float: ', kUSFloatFormatString);
  475.         Writeln('user float: ', theString);
  476.         FormatRecordToUserString(theIntegerFormatRecord, theString);
  477.         Writeln('US integer: ', kUSIntegerFormatString);
  478.         Writeln('user integer: ', theString);
  479.         FormatRecordToUserString(theScientificFormatRecord, theString);
  480.         Writeln('US scientific: ', kUSScientificFormatString);
  481.         Writeln('user scientific: ', theString);
  482.     END;
  483.  
  484.  
  485.     PROCEDURE DoCustomizedFormattingTest;
  486.  
  487.         CONST
  488.             kExtendedInputFormatString = '###,###.##;(###,###.##);0.##';
  489.             kIntegerInputFormatString = '#,###,###,###;(#,###,###,###);0';
  490.  
  491.         PROCEDURE WriteExtended (theNumber: Extended);
  492.             VAR
  493.                 theString: Str255;
  494.         BEGIN
  495.             FormatNumber(theNumber, theScientificFormatRecord, theString);
  496.             Writeln(theString);
  497.         END;
  498.  
  499.         FUNCTION ReadExtended (VAR theNumber: Extended): Boolean;
  500.             VAR
  501.                 theString: Str255;
  502.                 theFormatRecord: NumFormatString;
  503.         BEGIN
  504.             FailEOF;
  505.             ReadLn(theString);
  506.             PredefinedStringToFormatRecord(kExtendedInputFormatString, gUseAlternateNumerals, theFormatRecord);
  507.             ReadExtended := InterpretExtended(theString, theFormatRecord, theNumber);
  508.         END;
  509.  
  510.         PROCEDURE WriteInteger (theNumber: Extended);
  511.             VAR
  512.                 theString: Str255;
  513.                 theFormatRecord: NumFormatString;
  514.         BEGIN
  515.             FormatNumber(theNumber, theIntegerFormatRecord, theString);
  516.             Writeln(theString);
  517.         END;
  518.  
  519.         FUNCTION ReadInteger (VAR theNumber: LongInt): Boolean;
  520.             VAR
  521.                 theString: Str255;
  522.                 theFormatRecord: NumFormatString;
  523.         BEGIN
  524.             FailEOF;
  525.             ReadLn(theString);
  526.             PredefinedStringToFormatRecord(kIntegerInputFormatString, gUseAlternateNumerals, theFormatRecord);
  527.             ReadInteger := InterpretInteger(theString, theFormatRecord, theNumber);
  528.         END;
  529.  
  530.         VAR
  531.             theExtended: Extended;
  532.             theLongInt: LongInt;
  533.  
  534.     BEGIN
  535.         WriteLn('Testing user-specified formatting routines:');
  536.         WriteLn('Writing test numbers:');
  537.         WriteExtended(3.1415926);
  538.         WriteExtended(-3.1415926);
  539.         WriteExtended(0);
  540.         WriteInteger(1024);
  541.         WriteInteger(-1024);
  542.         WriteInteger(0);
  543.  
  544.         Writeln('Please enter integer numbers - 0 will skip to next test');
  545.         REPEAT
  546.             IF ReadInteger(theLongInt) THEN BEGIN
  547.                     IF Abs(theLongInt) < 999999 THEN
  548.                         WriteInteger(theLongInt)
  549.                     ELSE
  550.                         WriteLn('### Can''t display this number!')
  551.                 END
  552.             ELSE BEGIN
  553.                     WriteLn('### Can''t interpret this number!');
  554.                     theLongInt := 1; { to keep running }
  555.                 END;
  556.         UNTIL theLongInt = 0;
  557.  
  558.         Writeln('Please enter floating point numbers - 0', gDefaultDecimalSeparator, '0 will end test');
  559.         REPEAT
  560.             IF ReadExtended(theExtended) THEN BEGIN
  561.                     IF Abs(theExtended) < 999999.995 THEN
  562.                         WriteExtended(theExtended)
  563.                     ELSE
  564.                         WriteLn('### Can''t display this number!');
  565.                 END
  566.             ELSE BEGIN
  567.                     WriteLn('### Can''t interpret this number!');
  568.                     theExtended := 1.0; { to keep running }
  569.                 END;
  570.         UNTIL theExtended = 0.0;
  571.  
  572.     END;
  573.  
  574.  
  575.     VAR
  576.         answer: Str255;
  577.  
  578.  
  579. BEGIN
  580.  
  581.     CheckConfiguration;
  582.     InitializeDefaultNumberSeparators;
  583.     InitializeNumberPartsTables;
  584.     IF HasAlternateNumerals(gUserNumberPartsTable) THEN BEGIN
  585.             Writeln('Use alternate numerals [y/n]?');
  586.             FailEOF;
  587.             ReadLn(answer);
  588.             gUseAlternateNumerals := (Length(answer) > 0) & (answer[1] IN ['y', 'Y']);
  589.         END
  590.     ELSE
  591.         gUseAlternateNumerals := FALSE;
  592.     DoDefaultFormattingTest;
  593.     DoFormatStringTest;
  594.     DoCustomizedFormattingTest;
  595.     DisposeNumberPartsTables;
  596.  
  597. END.